• ABOUT
  • 2018
  • 2019
  • 2020
  • 2021
  • 2022
  • COMPARAISON
In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sbn
from urllib.request import urlopen
import squarify
import folium
import json
import plotly.express as px
import warnings
warnings.filterwarnings('ignore')

Importation des datasets¶

In [2]:
Data = pd.read_csv('valeursfoncieres-2020.txt', sep='|')

Visualisation du dataset non nettoyé¶

In [3]:
Data
Out[3]:
Identifiant de document Reference document 1 Articles CGI 2 Articles CGI 3 Articles CGI 4 Articles CGI 5 Articles CGI No disposition Date mutation Nature mutation ... Surface Carrez du 5eme lot Nombre de lots Code type local Type local Identifiant local Surface reelle bati Nombre pieces principales Nature culture Nature culture speciale Surface terrain
0 NaN NaN NaN NaN NaN NaN NaN 1 07/01/2020 Vente ... NaN 0 NaN NaN NaN NaN NaN T NaN 1061.0
1 NaN NaN NaN NaN NaN NaN NaN 1 02/01/2020 Vente ... NaN 0 NaN NaN NaN NaN NaN BT NaN 85.0
2 NaN NaN NaN NaN NaN NaN NaN 1 02/01/2020 Vente ... NaN 0 NaN NaN NaN NaN NaN T NaN 1115.0
3 NaN NaN NaN NaN NaN NaN NaN 1 02/01/2020 Vente ... NaN 0 NaN NaN NaN NaN NaN T NaN 1940.0
4 NaN NaN NaN NaN NaN NaN NaN 1 02/01/2020 Vente ... NaN 0 NaN NaN NaN NaN NaN T NaN 1148.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
3514693 NaN NaN NaN NaN NaN NaN NaN 1 16/12/2020 Vente ... NaN 0 3.0 Dépendance NaN 0.0 0.0 S NaN 447.0
3514694 NaN NaN NaN NaN NaN NaN NaN 1 16/12/2020 Vente ... NaN 0 2.0 Appartement NaN 64.0 2.0 S NaN 447.0
3514695 NaN NaN NaN NaN NaN NaN NaN 1 16/12/2020 Vente ... NaN 0 2.0 Appartement NaN 33.0 1.0 S NaN 447.0
3514696 NaN NaN NaN NaN NaN NaN NaN 1 16/12/2020 Vente ... NaN 0 2.0 Appartement NaN 40.0 2.0 S NaN 447.0
3514697 NaN NaN NaN NaN NaN NaN NaN 1 08/10/2020 Adjudication ... NaN 2 2.0 Appartement NaN 21.0 1.0 NaN NaN NaN

3514698 rows × 43 columns

Nettoyage de la donnée¶

In [4]:
columns_to_keep = ['Date mutation','Nature mutation','Valeur fonciere','Code postal','Commune','Code departement','Code commune','Nombre de lots','Code type local','Type local','Surface reelle bati','Nombre pieces principales','Surface terrain']
Data['Date mutation'] = pd.to_datetime(Data['Date mutation'])
Data['Code departement'] = Data['Code departement'].astype(str)
Data = Data[columns_to_keep]

Data = Data.dropna()
Data['Valeur fonciere'] = pd.to_numeric(Data['Valeur fonciere'].str.replace(',', '.'))
Data
Out[4]:
Date mutation Nature mutation Valeur fonciere Code postal Commune Code departement Code commune Nombre de lots Code type local Type local Surface reelle bati Nombre pieces principales Surface terrain
11 2020-09-01 Vente 72000.0 1270.0 COLIGNY 1 108 0 1.0 Maison 35.0 2.0 381.0
13 2020-06-01 Vente 180300.0 1000.0 BOURG-EN-BRESSE 1 53 0 1.0 Maison 75.0 4.0 525.0
16 2020-03-01 Vente 350750.0 1000.0 SAINT-DENIS-LES-BOURG 1 344 0 1.0 Maison 201.0 7.0 1497.0
17 2020-03-01 Vente 350750.0 1000.0 SAINT-DENIS-LES-BOURG 1 344 0 1.0 Maison 201.0 7.0 1267.0
18 2020-03-01 Vente 350750.0 1000.0 SAINT-DENIS-LES-BOURG 1 344 0 3.0 Dépendance 0.0 0.0 1267.0
... ... ... ... ... ... ... ... ... ... ... ... ... ...
3514692 2020-12-16 Vente 1937500.0 75004.0 PARIS 04 75 104 0 4.0 Local industriel. commercial ou assimilé 100.0 0.0 447.0
3514693 2020-12-16 Vente 1937500.0 75004.0 PARIS 04 75 104 0 3.0 Dépendance 0.0 0.0 447.0
3514694 2020-12-16 Vente 1937500.0 75004.0 PARIS 04 75 104 0 2.0 Appartement 64.0 2.0 447.0
3514695 2020-12-16 Vente 1937500.0 75004.0 PARIS 04 75 104 0 2.0 Appartement 33.0 1.0 447.0
3514696 2020-12-16 Vente 1937500.0 75004.0 PARIS 04 75 104 0 2.0 Appartement 40.0 2.0 447.0

1006548 rows × 13 columns

Argent total dépensé par mois selon les types de mutation pendant l'année¶

In [5]:
MUTATIONS = Data['Nature mutation'].unique()
def plotMutations(mut, data, ax):

    for m in MUTATIONS:
        temp = data[data['Nature mutation'] == m]
        result = temp.groupby(temp['Date mutation'].dt.to_period("M"))['Valeur fonciere'].sum()
        result.index = result.index.to_timestamp()
        x = result.index
        y = result.values
        
        if m == mut:
            ax.plot(x, y, color="#0b53c1", lw=2.4, zorder=10)
            ax.scatter(x, y, fc="w", ec="#0b53c1", s=60, lw=2.4, zorder=12)
            ax.autoscale()    
        else:
            ax.plot(x, y, color="#BFBFBF", lw=1.5)
    
    ax.set_title(mut, fontfamily="DejaVu Sans", fontsize=14, fontweight=500)
    return ax
In [6]:
fig, axes = plt.subplots(2, 3, figsize=(14, 7.5))
for idx, (ax, mut) in enumerate(zip(axes.ravel(), MUTATIONS)):
    annotate = idx == 0
    plotMutations(mut, Data, ax)

On remarque que essentiellement le type de mutation sont des ventes¶

Autre visualisation du phénomène¶

In [7]:
data1 = Data[Data['Nature mutation'] =='Vente'] 
data1 = data1.groupby(by='Date mutation',sort='Date mutation')['Valeur fonciere'].count()


data2 = Data[Data['Nature mutation'] =='Vente terrain à bâtir'] 
data2 = data2.groupby(by='Date mutation',sort='Date mutation')['Valeur fonciere'].count()


data3 = Data[Data['Nature mutation'] =='Echange'] 
data3 = data3.groupby(by='Date mutation',sort='Date mutation')['Valeur fonciere'].count()


data4 = Data[Data['Nature mutation'] =="Vente en l'état futur d'achèvement"] 
data4 = data4.groupby(by='Date mutation',sort='Date mutation')['Valeur fonciere'].count()


data5 = Data[Data['Nature mutation'] =='Adjudication'] 
data5 = data5.groupby(by='Date mutation',sort='Date mutation')['Valeur fonciere'].count()


data6 = Data[Data['Nature mutation'] =='Expropriation'] 
data6 = data6.groupby(by='Date mutation',sort='Date mutation')['Valeur fonciere'].count()


plt.figure(figsize=(18,11))
plt.plot(data1.index, data1.values, "r--", color="red")
plt.plot(data2.index, data2.values, "r--", color="blue")
plt.plot(data3.index, data3.values, "r--", color="green")
plt.plot(data4.index, data4.values, "r--", color="yellow")
plt.plot(data5.index, data5.values, "r--", color="purple")
plt.plot(data6.index, data6.values, "r--", color="black")
plt.legend(['Vente','Vente terrain à bâtir', 'Echange',"Vente en l'état futur d'achèvement",'Adjudication','Expropriation'])
plt.title('Nombre de mutations par type au cours des mois, en cumulé')
plt.show()

Cela confirme encore que le type de mutation importante est la vente¶

Nombre et répartitions des différents types¶

In [8]:
df = Data.groupby(['Type local'])['Type local'].count()

plt.bar(df.index, df.values)
bars = ['Appartement', 'Dépendance', 'Industriel', 'Maison']
y_pos = np.arange(len(bars))
plt.xticks(y_pos, bars)
plt.title('Nombre de mutations par type')
Out[8]:
Text(0.5, 1.0, 'Nombre de mutations par type')
In [9]:
fig, ax = plt.subplots()
ax.pie(df, labels=df.index, autopct='%1.1f%%')
ax.set_title('Proportion des types de locaux sur le nombre total de mutations')

plt.show()

Répartition des types de biens suivant différents caractères¶

In [10]:
df=Data[Data["Surface terrain"]< 5000]
plt.figure(figsize=(18,10))
plt.xticks(rotation=25)
sbn.violinplot(x = "Type local",y="Surface terrain", data=df)
plt.title('Répartition des types de locaux selon la surface de leur terrain')
Out[10]:
Text(0.5, 1.0, 'Répartition des types de locaux selon la surface de leur terrain')
In [11]:
df=Data[(Data["Surface reelle bati"]< 1000) & (Data["Surface reelle bati"].notna())]
plt.figure(figsize=(18,10))
plt.xticks(rotation=25)
sbn.violinplot(x = "Type local",y="Surface reelle bati", data=df)
plt.title('Répartition des types de locaux selon leur surface réelle bâtie')
Out[11]:
Text(0.5, 1.0, 'Répartition des types de locaux selon leur surface réelle bâtie')
In [12]:
df = Data[Data['Valeur fonciere'] < 2000000]
plt.figure(figsize=(18,10))
plt.xticks(rotation=25)
sbn.violinplot(x="Type local",y="Valeur fonciere",data=df)
plt.title('Répartition des types de locaux selon leur valeur foncière')
Out[12]:
Text(0.5, 1.0, 'Répartition des types de locaux selon leur valeur foncière')

Analyse des données par département¶

In [13]:
df = Data.groupby(['Code departement'])['Nature mutation'].count().sort_values(ascending=True)
plt.figure(figsize=(10,20))

plt.hlines(y=df.index, xmin=0, xmax=df.values, color='black')
plt.plot(df.values, df.index, "o", color="blue")
 

    
plt.title('Nombre de mutations par département')
plt.xlabel('Nombre de mutations')
plt.ylabel('Numéros de départements')
plt.show()
In [14]:
df1 = Data.groupby(['Code departement'])['Nature mutation'].count()

df = Data[((Data['Nature mutation']=='Vente') & ((Data['Type local'] == 'Maison') | (Data['Type local'] == 'Appartement')))]
df = Data.groupby(['Code departement'])['Nature mutation'].count()
plt.figure(figsize=(10,20))

plt.hlines(y=df1.index, xmin = 0, xmax = df1.values, color='red')
plt.hlines(y=df.index, xmin=0, xmax=df.values, color='skyblue')
plt.plot(df.values, df.index, "o")
plt.plot(df1.values, df1.index, "x", color="white")

plt.title("Nombre de mutations par département (ronds) et nombre de mutations par département en considérant uniquement les ventes d'appartements et de maisons (croix)")
plt.xlabel('Nombre de mutations')
plt.ylabel('Numéros de départements')

plt.show()

On observe que les maisons et appartements constitute la majeure partie des mutations¶

In [15]:
df_filtered = Data.loc[(Data['Nature mutation'] == 'Vente') & (Data['Type local'].isin(['Maison', 'Appartement']))]
df_mutations = df_filtered.groupby(['Code departement'])['Nature mutation'].count().reset_index()
df_mutations = df_mutations.sort_values(by='Nature mutation', ascending=False)
df_filtered2 = df_filtered.groupby(['Code departement', 'Type local'])['Type local'].count().unstack()
df_filtered2 = df_filtered2.fillna(0)
df_filtered2['Total'] = df_filtered2.sum(axis=1)
df_filtered2['% Maison/Appartement'] = ((df_filtered2['Maison'] + df_filtered2['Appartement']) / df_filtered2['Total']) * 100

# Trier les départements par pourcentage de ventes d'appartements et de maisons décroissant
df_filtered2 = df_filtered2.sort_values(by='% Maison/Appartement', ascending=False)

# Créer le graphique
fig, ax = plt.subplots(figsize=(18, 12))
sbn.barplot(x='% Maison/Appartement', y=df_filtered2.index, data=df_filtered2, color='skyblue')
ax.set_title("Pourcentage de ventes d'appartements et de maisons par département")
ax.set_xlabel('% Maison/Appartement')
ax.set_ylabel('Numéro de département')

plt.show()
In [16]:
myscale = None

def mapping_france_folium(data):
    map = folium.Map(location=[48.862, 2.346], zoom_start = 5)
    departments = f"https://france-geojson.gregoiredavid.fr/repo/departements.geojson"
    d = {'Code': data.index, 'Valeur': np.log(data.values)}
    da = pd.DataFrame(d)

    folium.Choropleth(geo_data=departments, 
    data=da, 
    columns=['Code', 'Valeur'], 
    key_on='properties.code',
    fill_color= "PuRd",
    fill_opacity=1,
    line_opacity=.1).add_to(map)
    
    folium.LayerControl().add_to(map)
    return map

def mapping_Paris_circle(data, bigNumbers = False):
    map = folium.Map(location = [48.856578, 2.351828], zoom_start = 12)
    arr = json.load(open("arrondissements.geojson"))
    d = {'Code': data.index, 'Valeur': data.values}
    da = pd.DataFrame(d)
    for a in arr["features"]:
        prop = a["properties"]
        temp = da[da['Code'] == prop["c_arinsee"] - 100]
        temp = temp['Valeur'].values
        folium.Circle(prop["geom_x_y"], 
        fill=True,
        popup = prop["l_ar"],
        radius = (temp[0]/1) if not bigNumbers else temp[0]/9000000).add_to(map)
    return map


def mapping_Paris(data):
    map = folium.Map(location = [48.856578, 2.351828], zoom_start = 12)
    arr = json.load(open("arrondissements.geojson"))
    d = {'Code': data.index + 100, 'Valeur': np.log(data.values)}
    da = pd.DataFrame(d)
    da = da[(da['Code'] >= 75100) & (da['Code'] <= 75120)]
    myscale = np.linspace(da['Valeur'].min(), da['Valeur'].max(), 10)
    folium.Choropleth(geo_data=arr, 
            data=da, 
            columns=['Code', 'Valeur'], 
            key_on='properties.c_arinsee',
            fill_color= "PuRd",
            threshold_scale=myscale,
            fill_opacity=0.8,
            line_opacity=.1).add_to(map)
        
    
    folium.LayerControl().add_to(map)
    return map

def mapping_Lyon(data):
    map = folium.Map(location = [45.763420, 4.834277], zoom_start = 12)
    arr = json.load(open("lyon.json"))
    d = {'Code': data.index + 380, 'Valeur': np.log(data.values)}
    da = pd.DataFrame(d)
    da = da[(da['Code'] >= 69381) & (da['Code'] <= 69389)]
    folium.Choropleth(geo_data=arr, 
            data=da, 
            columns=['Code', 'Valeur'], 
            key_on='properties.insee',
            fill_color= "PuRd",
            threshold_scale=myscale,
            fill_opacity=0.8,
            line_opacity=.1).add_to(map)
    
    folium.LayerControl().add_to(map)
    return map

def mapping_Marseille(data):
    map = folium.Map(location = [43.296482, 5.36978], zoom_start = 12)
    arr = json.load(open("marseille.geojson"))
    d = {'Code': data.index + 200, 'Valeur': np.log(data.values)}
    da = pd.DataFrame(d)
    da = da[(da['Code'] >= 13201) & (da['Code'] <= 13216)]
    da['Code'] = da['Code'].astype(int).astype(str)
    folium.Choropleth(geo_data=arr, 
            data=da, 
            columns=['Code', 'Valeur'], 
            key_on='properties.DEPCO',
            fill_color= "PuRd",
            threshold_scale=myscale,
            fill_opacity=0.8,
            line_opacity=.1).add_to(map)
    
    folium.LayerControl().add_to(map)
    return map
In [17]:
data = Data.groupby(['Code departement'])['Nature mutation'].count()
map = mapping_france_folium(data)
map
Out[17]:
Make this Notebook Trusted to load map: File -> Trust Notebook

On voit ici, en échelle logarithmique, le nombre de mutations par département au cours de l'année.¶

In [18]:
data = Data[Data['Nature mutation'] == 'Vente'].groupby(['Code departement'])['Valeur fonciere'].sum()
map = mapping_france_folium(data)
map
Out[18]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Valeur cumulée des ventes par département en échelle logarithmique¶

In [19]:
data = Data[Data['Nature mutation'] == 'Vente']
data = data[data['Type local'] == 'Maison'].groupby(['Code departement'])['Valeur fonciere'].sum()
map = mapping_france_folium(data)
map
Out[19]:
Make this Notebook Trusted to load map: File -> Trust Notebook

On voit ci-dessus, en échelle logarithmique, la valeur cumulée des ventes de maisons par département au cours de l'année. Il est intéressant de noter que l'importance de Paris dans la carte précédente disparaît : très peu de maisons sont vendues à Paris même.¶

In [20]:
data = Data[(Data['Nature mutation'] == 'Vente') & ((Data['Type local'] == 'Maison') | (Data['Type local'] == 'Appartement'))]
data['prix_m2'] = data['Valeur fonciere']/data['Surface reelle bati']
data = data.groupby(['Code departement'])['prix_m2'].agg('mean')
map = mapping_france_folium(data)
map
Out[20]:
Make this Notebook Trusted to load map: File -> Trust Notebook

On voit ci-dessus, en échelle logarithmique, le prix au m2 par département.¶

In [21]:
data = Data[['Surface reelle bati','Valeur fonciere']]
fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(18,10))

# Premier graphique avec échelle logarithmique
ax1.set_xscale('log')
ax1.set_yscale('log')
ax1.scatter(data['Surface reelle bati'],data['Valeur fonciere'])
ax1.set_title('Répartition de la valeur foncière en fonction de la surface bâtie (échelle logarithmique)')
ax1.set_xlabel('Surface bâtie')
ax1.set_ylabel('Valeur foncière')

# Deuxième graphique sans échelle logarithmique
ax2.scatter(data['Surface reelle bati'],data['Valeur fonciere'])
ax2.set_title('Répartition de la valeur foncière en fonction de la surface bâtie')
ax2.set_xlabel('Surface bâtie')
ax2.set_ylabel('Valeur foncière')

plt.show()

On observe bien que l'apllication du log permet mieux gérer ces valeurs extremes¶

Analyse de Paris¶

Mutation par arrondissement¶

In [22]:
data = Data.groupby(['Code postal'])['Nature mutation'].count()
map = mapping_Paris_circle(data, False)
map
Out[22]:
Make this Notebook Trusted to load map: File -> Trust Notebook

En échelle logarithmique¶

In [23]:
data = Data.groupby(['Code postal'])['Nature mutation'].count()
map = mapping_Paris(data)
map
Out[23]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Valeur des ventes par arrondissement¶

In [24]:
data = Data[Data['Nature mutation'] == 'Vente']
data = data.groupby(['Code postal'])['Valeur fonciere'].sum()
map = mapping_Paris_circle(data, True)
map
Out[24]:
Make this Notebook Trusted to load map: File -> Trust Notebook
On observe que dans 2 arrondissements la valeur des ventes est bien supérieure aux autres arrondissement, en échelle logarithimique ci-dessous cela se quantifie plus facilement¶

On représente les mêmes données sur échelle logarithmique¶

In [25]:
data = Data[Data['Nature mutation'] == 'Vente']
data = data.groupby(['Code postal'])['Valeur fonciere'].sum()
map = mapping_Paris(data)
map
Out[25]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Comparons ces résultas à Lyon et marseille¶

In [26]:
data = Data[Data['Nature mutation'] == 'Vente']
data = data.groupby(['Code postal'])['Valeur fonciere'].sum()
map = mapping_Marseille(data)
map
Out[26]:
Make this Notebook Trusted to load map: File -> Trust Notebook
In [27]:
data = Data[Data['Nature mutation'] == 'Vente']
data = data.groupby(['Code postal'])['Valeur fonciere'].sum()
map = mapping_Lyon(data)
map
Out[27]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Analyse de corrélation entre les caractéristiques foncières en Ile-de-France¶

In [28]:
data = Data[Data['Code departement'] == '75']
data = data[['Valeur fonciere','Surface reelle bati','Surface terrain', 'Nombre pieces principales']]
sbn.heatmap(data.corr(), annot= True, cmap='Reds')
plt.xticks(rotation = 45)
plt.title('Corrélation entre la valeur foncière, la surface réelle bâtie, \nla surface du terrain et le nombre de pièces principales\n(en IDF))')
plt.show()
In [ ]: